---
title: Supabase
description: Integrate Stack Auth with Supabase RLS
---

This guide shows how to integrate Stack Auth with Supabase row level security (RLS).

<Info>
  This guide only focuses on the RLS/JWT integration and does not sync user data between Supabase and Stack. You should use [webhooks](/concepts/webhooks) to achieve data sync.
</Info>

## Setup

Let's create a sample table and some RLS policies to demonstrate how to integrate Stack Auth with Supabase RLS. You can apply the same logic to your own tables and policies.
<Steps>
### Setup Supabase
First, let's create a Supabase project, then go to the [SQL Editor](https://supabase.com/dashboard/project/_/sql/new) and create a new table with some sample data and RLS policies.

```sql title="Supabase SQL Editor"
-- Create the 'data' table
CREATE TABLE data (
  id bigint PRIMARY KEY,
  text text NOT NULL,
  user_id UUID
);

-- Insert sample data
INSERT INTO data (id, text, user_id) VALUES
  (1, 'Everyone can see this', NULL),
  (2, 'Only authenticated users can see this', NULL),
  (3, 'Only user with specific id can see this', NULL);

-- Enable Row Level Security
ALTER TABLE data ENABLE ROW LEVEL SECURITY;

-- Allow everyone to read the first row
CREATE POLICY "Public read" ON "public"."data" TO public
USING (id = 1);

-- Allow authenticated users to read the second row
CREATE POLICY "Authenticated access" ON "public"."data" TO authenticated
USING (id = 2);

-- Allow only the owner of the row to read it
CREATE POLICY "User access" ON "public"."data" TO authenticated
USING (id = 3 AND auth.uid() = user_id);
```

### Setup a new Next.js project

Now let's create a new Next.js project and install Stack Auth and Supabase client. (more details on [Next.js setup](https://nextjs.org/docs/getting-started/installation), [Stack Auth setup](../getting-started/setup.mdx), and [Supabase setup](https://supabase.com/docs/guides/getting-started/quickstarts/nextjs))

```bash title="Terminal"
npx create-next-app@latest -e with-supabase stack-supabase
cd stack-supabase
npx @stackframe/init-stack@latest
```

Now copy the environment variables from the Supabase dashboard to the `.env.local` file: 
- `NEXT_PUBLIC_SUPABASE_URL`
- `NEXT_PUBLIC_SUPABASE_ANON_KEY`
- `SUPABASE_JWT_SECRET`

Copy environment variables from the Stack dashboard to the `.env.local` file.
- `NEXT_PUBLIC_STACK_PROJECT_ID`
- `NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY`
- `STACK_SECRET_SERVER_KEY`

### Set up Supbase client

Now let's create a server action that mints a supabase JWT with the Stack Auth user ID if the user is authenticated.

```tsx title="/utils/actions.ts"
'use server';

import { stackServerApp } from "@/stack/server";
import * as jose from "jose";

export const getSupabaseJwt = async () => {
  const user = await stackServerApp.getUser();

  if (!user) {
    return null;
  }

  const token = await new jose.SignJWT({
    sub: user.id,
    role: "authenticated",
  })
    .setProtectedHeader({ alg: "HS256" })
    .setIssuedAt()
    .setExpirationTime('1h')
    .sign(new TextEncoder().encode(process.env.SUPABASE_JWT_SECRET));

  return token;
};
```

And now create a helper function to create a Supabase client with the JWT signed by the server action

```tsx title="/utils/supabase-client.ts"
import { createBrowserClient } from "@supabase/ssr";
import { getSupabaseJwt } from "./actions";

export const createSupabaseClient = () => {
  return createBrowserClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    { accessToken: async () => await getSupabaseJwt() || "" }
  );
}
```

### Fetch data from Supabase

Let's create an example page that fetches data from Supabase and displays it.

```tsx title="/app/page.tsx"
'use client';

import { createSupabaseClient } from "@/utils/supabase-client";
import { useStackApp, useUser } from "@stackframe/stack";
import Link from "next/link";
import { useEffect, useState } from "react";

export default function Page() {
  const app = useStackApp();
  const user = useUser();
  const supabase = createSupabaseClient();
  const [data, setData] = useState<null | any[]>(null);

  useEffect(() => {
    supabase.from("data").select().then(({ data }) => setData(data ?? []));
  }, []);

  const listContent = data === null ? 
    <p>Loading...</p> :
    data.length === 0 ?
      <p>No notes found</p> :
      data.map((note) => <li key={note.id}>{note.text}</li>);

  return (
    <div>
      {
        user ? 
        <>
          <p>You are signed in</p>
          <p>User ID: {user.id}</p>
          <Link href={app.urls.signOut}>Sign Out</Link>
        </> : 
        <Link href={app.urls.signIn}>Sign In</Link>
      }
      <h3>Supabase data</h3>
      <ul>{listContent}</ul>
    </div>
  )
}
```

Now you should be able to compare the data you can view with an anonymous user, an authenticated user. You can also add your user Id to the row 3 of the Supabase table, and you should be able to see the row if and only if you are signed in with that user.

</Steps>

You can find the full example [here on GitHub](https://github.com/stack-auth/stack-auth/tree/main/examples/supabase).
